/*
 * Filename: evict-cache.c
 * Created Date: Tuesday, June 30th 2020, 6:53:22 pm
 * Author: koyst
 * 
 * Copyright (c) 2020 Your Company
 */

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#if defined(__x86_64__)
#include <x86intrin.h>
#endif

#define PAGE_4K_MASK (~0xFFFLU)
#define CACHE_BLOCK 1024
// 4路相联
#define ASS_SIZE 4
// 相联度2位(4), block 8位(256B)
#define CACHE_INX_SHIFT (6 + 2)
// 8组
#define CACHE_GROUP_SIZE 256

// uint64_t cache_invaild_buf[1024*1024 * 1024]; // 8M
#define cache_invaild_buf_size (1024 * 1024)
uint64_t* cache_invaild_buf = NULL;
uint64_t* cache_emu[CACHE_GROUP_SIZE][ASS_SIZE]; // 每个组16项, 共

uint64_t temp_val;
/* 
4 way / line 64bytes
64KB

64KB/64/4 = 256 set

I = (Am / Nw / Na ) mod N
N = 256
Na = 4
Nw = 64
 */

static void init_cache_emu()
{
    cache_invaild_buf = malloc(cache_invaild_buf_size);

    if (cache_invaild_buf == NULL) {
        printf("malloc error\n");
        exit(1);
    }

    memset(cache_invaild_buf, 1, cache_invaild_buf_size);

    cache_invaild_buf = (((uint64_t)(cache_invaild_buf)) + 64) & (~0x3fUL); // align cache line addr

    for (int g = 0; g < CACHE_GROUP_SIZE; g++) {
        for (int i = 0; i < ASS_SIZE; i++) {
            cache_emu[g][i] = NULL;
        }
    }

    for (int i = 0; i < cache_invaild_buf_size / 8 - 8; i += 8) {
        int g = (((uint64_t)(cache_invaild_buf + i)) >> CACHE_INX_SHIFT) % CACHE_GROUP_SIZE;
        for (int j = 0; j < ASS_SIZE; j++) {
            if (cache_emu[g][j] == NULL)
                cache_emu[g][j] = cache_invaild_buf + i;
        }
    }

    for (int g = 0; g < CACHE_GROUP_SIZE; g++) {
        for (int i = 0; i < ASS_SIZE; i++) {
            if (cache_emu[g][i] == NULL) {
                printf("error g: %d, i: %d is null\n", g, i);
                exit(2);
            }
        }
    }
}

static inline void dsbsy()
{
#if defined(__x86_64__)
    _mm_mfence();
#else
    asm("sync");
#endif
}

static void clflush(void* addr)
{
    uint64_t n_addr = (uint64_t)addr;
    int inx = (n_addr >> CACHE_INX_SHIFT) % CACHE_GROUP_SIZE;
    // 模拟填充cache
    // for (int j = 0; j < 10; j++)
    for (int i = 0; i < ASS_SIZE; i++) {
        n_addr = *(cache_emu[inx][i]);
        *(cache_emu[inx][i]) = *(cache_emu[inx][i]);
    }

    // for(int i=0; i< cache_invaild_buf_size; i++){
    //     n_addr = cache_invaild_buf
    // }

    // _mm_clflush(addr);
    // _mm_mfence();

    dsbsy();
}


static void hw_clflush(void* addr)
{
    // _mm_clflush(addr);
    // dsbsy();
}

int debug = 0;
static struct timespec time_start = { 0, 0 }, time_end = { 0, 0 };

long time_read(uint8_t* addr)
{
    long ns = 0;
    clock_gettime(CLOCK_MONOTONIC, &time_start);
    dsbsy();
    clock_gettime(CLOCK_MONOTONIC, &time_start);
    dsbsy();
    temp_val &= *addr;
    dsbsy();
    clock_gettime(CLOCK_MONOTONIC, &time_end);
    // ns = (time_end.tv_sec - time_start.tv_sec) * 1000000000 + (time_end.tv_nsec - time_start.tv_nsec);
    ns = (time_end.tv_nsec - time_start.tv_nsec);
    return ns;
}

void duangduang()
{
    FILE* f = fopen("/dev/null", "r");
    if (f)
        fclose(f);
    dsbsy();
}

void debug_time(uint64_t* ptr, uint32_t size, uint32_t step)
{
    long atime;
    for (int i = 0; i < size; i += step) {
        atime = time_read(&ptr[i]);
        printf("item: %p: %ld\n", &ptr[i], atime);
    }
}

uint64_t cache_fill[16 * 1024 * 8 * 16]; // 6M
void fill_cache()
{
    uint64_t tmp;
    // tmp = aa[0];
    for (int i = 0; i < 16 * 1024 * 8 * 16; i += 16) {
        tmp = cache_fill[i];
    }
}

void test_time()
{
#define step 16
    uint64_t aa[10 * step];
    uint64_t* ptr = &aa[0];
    long t1, t2, t3;

    fill_cache();

    t1 = time_read(ptr);
    duangduang();

    t2 = time_read(ptr);
    duangduang();

    clflush(ptr);
    // hw_clflush(ptr);
    // hw_clflush(ptr);
    // hw_clflush(ptr);
    t3 = time_read(ptr);

    // hw_clflush(ptr);
    printf("item: %p: %ld, %ld, %ld\n", ptr, t1, t2, t3);

    // printf("first=============\n");
    // debug_time(aa, 10 * 1, step);
    // // clflush(&aa[0]);
    // hw_clflush(&aa[0*step]);
    // // hw_clflush(&aa[1*step]);
    // printf("second=============\n");
    // debug_time(aa, 10 * 1, step);
}

int main(int argc, char const* argv[])
{
    init_cache_emu();
    for (int i = 0; i < 4; i++)
        test_time();
    test_time();
    test_time();
    return 0;
}
